<?php
/**
 * NetBiz Framework
 * Copyright (C) 20009 - 2010 NetBiz
 * 
 * All rights reserved.
 */
 
require_once 'DB/DataObject.php';

class FRM_DataObject extends DB_DataObject // implements Iterator
{
	private $error = 0;
	private $_pager = NULL;
	
	// :KLUDGE: needs to make this function protected
	public function setPager(FRM_Pager $p)
	{
		$this->_pager = $p;
	}

	function _connect()
	{
		global $_DB_DATAOBJECT;
		if (!$this->_database_dsn_md5 || empty($_DB_DATAOBJECT['CONNECTIONS'][$this->_database_dsn_md5]))
		{
			if (!($result = parent::_connect()) instanceof PEAR_Error)
			{
				if (FRM_Config::get('I18N','UTF8_SUPPORT'))
				{
					// Connection was ok, we can issue a SET NAMES query for i18n
					$_DB_DATAOBJECT['CONNECTIONS'][$this->_database_dsn_md5]->query("SET NAMES 'utf8';");
				}
				// Set time_zone using PHP to benefit of application timezone config
				$_DB_DATAOBJECT['CONNECTIONS'][$this->_database_dsn_md5]->query("SET time_zone = '".date('P')."'");
			}
		}
		else
		{
			$result = parent::_connect();
		}

		if ($result instanceof PEAR_Error)
		{
			throw new FRM_Exception('Database connection failed, see extra information section.',FRM_Exception::DATABASE_CONNECTION,$result);
		}

		return $result;
	}

	/*public function joinAdd($obj = false, $joinType='INNER', $joinAs=false, $joinCol=false, $joinCond = null)
	{
		parent::joinAdd($obj, $joinType, $joinAs, $joinCol);
		
		if (isset($joinCond)) {
			preg_match('/JOIN(.*)/', $this->_join, $result);
			if (strpos($result, $obj->_database.".".$obj->__table.".") === false) {
				$this->_join .= "AND " . $joinCond;
			} else {
				$this->_join = preg_replace('/JOIN(.*)/', 'JOIN\1 AND ' . $joinCond, $this->_join, 1);
			}
			
		}
	}*/
	
	public function fetchList()
	{
		$ret = array();
		
		while($this->fetch()) {
			$ret[] = $this->toArray();
		}
		
		return $ret;
	}

	public function query($sql) {
		// Activate the pagination system
		if ($this->_pager instanceof FRM_Pager)
		{
			$sql = $this->_pager->page($this, $sql);
		}
		//

		if (FRM_Config::get('LOG','ENABLE_ORM_LOG'))
		{
			$logger = FRM::load('logger');
			$sql_q = rtrim(substr($sql,0,FRM_Config::get('LOG','MAX_SQL_QUERY_LENGTH')));
		}

		$ret = parent::query($sql);

		if ($ret instanceof PEAR_Error)
		{
			throw new FRM_Exception('Error in SQL query',FRM_Exception::SQL_ERROR,$ret);
		}
		elseif (FRM_Config::get('LOG','ENABLE_ORM_LOG'))
		{
			// We log function at the debug level to store the number of object returned
			// and a summary of the query
			$logger->debug('Query() returned '. intval($ret).'. query : '.$sql_q);
		}
		
		
		return $ret;
	}

	public function find($n = false)
	{
		// Activate the pagination system
		if ($this->_pager instanceof FRM_Pager)
		{
			$this->_pager->page($this);
		}
		//

		$ret = parent::find($n);

		if ($ret instanceof PEAR_Error)
		{
			throw new FRM_Exception('Error in SQL find',FRM_Exception::SQL_ERROR,$ret);
		}
		elseif (FRM_Config::get('LOG','ENABLE_ORM_LOG'))
		{
			$logger = FRM::load('logger');
			// We log function at the debug level to store the number of object returned
			// and a summary of the query
			$logger->debug('Find() returned '. intval($ret).' records.');
		}

		return $ret;
	}

	public function insert() {
		$ret = parent::insert();		

		if ($ret === false)
		{
			throw new FRM_Exception('Error in SQL insert',FRM_Exception::SQL_ERROR,$this->_lastError);
		}
		elseif (FRM_Config::get('LOG','ENABLE_ORM_LOG'))
		{
			$logger = FRM::load('logger');
			// We log function at the debug level to store the number of object returned
			// and a summary of the query
			$logger->debug('Insert() returned '. intval($ret));
		}

		return $ret;
	}
	
	public function update($dataObject = false) {
		$ret = parent::update($dataObject);
		
		if ($ret === false)
		{
			throw new FRM_Exception('Error in SQL update',FRM_Exception::SQL_ERROR,$this->_lastError);
		}
		elseif (FRM_Config::get('LOG','ENABLE_ORM_LOG'))
		{
			$logger = FRM::load('logger');
			// We log function at the debug level to store the number of object returned
			// and a summary of the query
			$logger->debug('Update() returned '. intval($ret));
		}
		
		return $ret;
	}
	
	public function delete($useWhere = false) {
		$ret = parent::delete($useWhere);
		
		if ($ret === false)
		{
			throw new FRM_Exception('Error in SQL delete',FRM_Exception::SQL_ERROR,$this->_lastError);
		}
		elseif (FRM_Config::get('LOG','ENABLE_ORM_LOG'))
		{
			$logger = FRM::load('logger');
			// We log function at the debug level to store the number of object returned
			// and a summary of the query
			$logger->debug('Delete() returned '. intval($ret));
		}
		
		return $ret;
	}

	public function count($countWhat = FALSE, $whereAddOnly = FALSE)
	{
		if (!$this->_query['group_by'] or $whereAddOnly)
		{
			return parent::count($countWhat, $whereAddOnly);
		}
		else
		{
			// Build an envelope query
			// :WARNING: this is sooo performance un-wise
			$sql = $this->getSelectSQL();
			$count_sql = 'SELECT COUNT(1) AS cnt FROM (' . $sql . ') _cnt;';

			// Do the query, get the result, clean the mess
			$ret = $this->_query($count_sql);

			if (!($ret instanceof PEAR_Error))
			{
				if ($this->fetch())
				{
					$ret = $this->cnt;
					$this->free();
				}
				else
				{
					$ret = 0;
				}
			}
			else
			{
				throw new FRM_Exception('Error when counting records in SQL query',FRM_Exception::SQL_ERROR,$ret);
			}

			return $ret;
		}
	}
	
	public function startTransaction() {
		$this->query('BEGIN');
	}
	
	public function commitTransaction() {
		$this->query('COMMIT');
	}
	
	public function rollbackTransaction() {
		$this->query('ROLLBACK');
	}


	/**
	 * Build and return the actual SQL query represented by the object
	 *
	 * @see DB_DataObject::find
	 */
	public function getSelectSQL()
	{
		global $_DB_DATAOBJECT;
		// connect will load the config!
		$this->_connect();
		$DB = &$_DB_DATAOBJECT['CONNECTIONS'][$this->_database_dsn_md5];
		$query_before = $this->_query;
		$this->_build_condition($this->table()) ;

		$quoteIdentifiers = !empty($_DB_DATAOBJECT['CONFIG']['quote_identifiers']);

		// We ignore the limit
		$sql = 'SELECT ' .
			$this->_query['data_select'] . " \n" .
			' FROM ' . ($quoteIdentifiers ? $DB->quoteIdentifier($this->__table) : $this->__table) . " \n" .
			$this->_join . " \n" .
			$this->_query['condition'] . " \n" .
			$this->_query['group_by']  . " \n" .
			$this->_query['having']    . " \n" .
			$this->_query['order_by']  . " \n" .
			( (isset($this->_query['limit_start']) && strlen($this->_query['limit_start'] . $this->_query['limit_count'])) ?
				' LIMIT ' . $this->_query['limit_start'] . ', ' . $this->_query['limit_count'] : '' );

		$this->_query = $query_before;
		return $sql;
	}


	/**
	 * Returns an associative array with all pagination info for the object
	 * Useful in a template inclusion context, like for example :
	 * {include file='component/pager.tpl' data=$myobj->getPaginationData()}
	 *
	 * Elements in the results array :
	 *   - 'current' : int, the current page index
	 *   - 'previous' : mixed, the previous page index, FALSE if none
	 *   - 'next' : mixed, the next page index, FALSE if none
	 *   - 'first' : bool, TRUE if the current page is the first one ( = !previous)
	 *   - 'last' : bool, TRUE if the current page is the last one ( = !next)
	 *   - 'total' : int, the total number of page
	 *   - 'arg' : string, the argument name for the page index
	 *   - 'iter' : An iterator on the pages that should be displayed, @see FRM_Pager_IteratorInterface
	 *
	 * @see FRM_Pager::getData()
	 * @return array
	 */
	public function getPaginationData()
	{
		if (!$this->_pager instanceof FRM_Pager)
		{
			$this->_pager = new FRM_Pager('',1);
		}

		return $this->_pager->getData();
	}

	/**
	 * Wrapping all DataObject functions to provide complete error logging
	 * and handling mechanism.
	 */
	/*public function get; //false
	public function whereAdd; //err
	public function orderBy; //err-false
	public function groupBy; //err-false
	public function having; //err-false
	public function limit; //err-false
	public function selectAdd; //err-false
	public function selectAs; //false
	public function fetchRow; //false
	public function count; //false
	public function joinAdd; //err-false*/

	/**
	 * Iterator implementation.
	 *
	 * Usage :
	 *  $m = new MyOjbect();
	 *  // No find needed here
	 *
	 *  foreach($m as $elem)
	 *  {
	 *    ...
	 *  }
	 *
	 * Note that no find() call is required, note also that each foreach call on
	 * the object will trigger a new SQL query. Also, don't update(), delete() or
	 * alter the element object in the loop or you will break the running loop.
	 */
	/*protected $iterator_valid = FALSE;
	
	public function rewind() {
		$this->iterator_valid = TRUE;
		$this->find();
	}
		
	public function current() {
		return $this;
	}
		
	public function key() {
		// to be implemented
		return 0;
	}
		
	public function next() {
		$this->iterator_valid = intval($this->fetch());
		return $this;
	}
		
	public function valid() {
		return $this->iterator_valid;
	}*/

	/**
	 * Free all resources used by DB_DataObject.
	 * This function is the same as if we were calling free()
	 * on each DataObject created so far.
	 *
	 * These register are free'd :
	 *  - SQL Results fields cache
	 *  - SQL Results resources
	 *  - Static get cache
	 *  - DB Connection num rows (apparently a bug in DB)
	 *
	 * @return null
	 */
	public static function freeAll()
	{
		global $_DB_DATAOBJECT;

		foreach ($_DB_DATAOBJECT['RESULTFIELDS'] as $k => $v)
		{
			unset($_DB_DATAOBJECT['RESULTFIELDS'][$k]);
		}

		foreach ($_DB_DATAOBJECT['RESULTS'] as $k => $v)
		{
			unset($_DB_DATAOBJECT['RESULTS'][$k]);
		}

		foreach ($_DB_DATAOBJECT['CACHE'] as $k => $v)
		{
			unset($_DB_DATAOBJECT['CACHE'][$k]);
        	}
		
		foreach ($_DB_DATAOBJECT['CONNECTIONS'] as $k => $v)
		{
			$_DB_DATAOBJECT['CONNECTIONS'][$k]->num_rows = array();
		}
	}
}